package org.gtugs.omsk.funnycats.engine;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.gtugs.omsk.funnycats.engine.GameEffect.EffectType;
import org.gtugs.omsk.funnycats.engine.beans.Position;
import org.gtugs.omsk.funnycats.engine.beans.RectArea;
import org.gtugs.omsk.funnycats.engine.beans.Size;
import org.gtugs.omsk.funnycats.engine.beans.Vector;

public class GameWorld{
	
	public static class ContactEvent{
		public final GameObject firstObject;
		public final GameObject secondObject;
		public ContactEvent(GameObject firstObject, GameObject secondObject) {
			this.firstObject = firstObject;
			this.secondObject = secondObject;
		}
	}
	
	public static interface ContactListener{
		public void contant(ContactEvent contactEvent);
	}
	
	public static class DirectionChangedEvent{
		public final GameObject directionChangedObject;
		
		public DirectionChangedEvent(GameObject directionChangedObject){
			this.directionChangedObject = directionChangedObject;
		}
	}
	
	public static interface OnXDirectionChangedListener{
		public void onDirectionChanged(DirectionChangedEvent event);
	}
	
	public void addOnXDirectionChangedListener(OnXDirectionChangedListener directionChangedListener){
		this.mDirectionChangedListener = directionChangedListener;
	}
	
	public GameWorld(Size size) {
		this.gameWorldSize = size;
		gameWorldObjects = new ArrayList<GameObject>();
		
		effects.put(EffectType.ACCELERATE, new GameEffect(EffectType.ACCELERATE, 5f,1f,1f,1f,1f));
		effects.put(EffectType.SLOW, new GameEffect(EffectType.SLOW, 5f,1f,1f,1f,1f));
		effects.put(EffectType.STOP, new GameEffect(EffectType.STOP, 5f,0f,0f,0f,0f));
		
		/*
		addContactListener(new ContactListener() {
			@Override
			public void contant(ContactEvent contactEvent) {
				FunnyCatsApplication.Log.d("ContactEvent", "Contact");
			}
		});
		
		addOnXDirectionChangedListener(new OnXDirectionChangedListener() {
			@Override
			public void onDirectionChanged(DirectionChangedEvent event) {
				FunnyCatsApplication.Log.d("DirectionChanged", "xFlip");
			}
		});
		*/
	}
	
	public void addContactListener(ContactListener contactListener){
		mContactListener = contactListener;
	}

	public void addGameObject(GameObject object, Position position, Vector speed, float lifetime){
		object.setCenterPosition(position);
		object.setSpeed(speed);
		object.setLifetime(lifetime);
		
		gameWorldObjects.add(object);
	}
	
	public void addGameObject(GameObject object, Position position){
		addGameObject(object, position, Vector.zeroVector(), -1);
	}
	
	public void addGameObject(GameObject object, Position position, Vector speed){
		addGameObject(object, position, speed, -1);
	}
	
	public void addGameObject(GameObject object, Position position, float lifetime){
		addGameObject(object, position, Vector.zeroVector(), lifetime);
	}
		
	public void removeObject(GameObject object){
		gameWorldObjects.remove(object);
	}
	
	public void setSpeedForObject(GameObject gameObject, float speed){
		final Vector currentSpeed = gameObject.getSpeed();
		final Vector newSpeed = Vector.makeFromAngle(speed, currentSpeed.calculateAngle());
		
		if(newSpeed.length() < gameObject.getMaxSpeed()){
			gameObject.setSpeed(newSpeed);
		} else {
			setSpeedForObject(gameObject, gameObject.getMaxSpeed());
		}
	}
	
	public void setTargetForObject(GameObject object, Position target){
		final Position p = object.getCenterPosition();
		target = getPossibleTarget(object, target);
		if((target.x == p.x) && (target.y == p.y))return;
				
		objectTargets.put(object, target);
		final float angle = Vector.make(target.x - p.x, target.y - p.y).calculateAngle();
		object.setSpeed(Vector.makeFromAngle(object.getMaxSpeed(), angle));
	}
	
	protected Position getPossibleTarget(GameObject object, Position target){
		final float border_left = object.getOccupiedArea().size.width/2;
		final float border_right= gameWorldSize.width - object.getOccupiedArea().size.width/2;
		final float border_top  = gameWorldSize.height - object.getOccupiedArea().size.height/2;
		final float border_bottom=object.getOccupiedArea().size.height/2;
		
		final float x1 = object.getCenterPosition().x;
		final float y1 = object.getCenterPosition().y;
		
		final float x2 = target.x;
		final float y2 = target.y;
				
		if((x1 == x2) && (y1 == y2)){
			return Position.make(target.x, target.y);
		}
		
		if(x1 == x2){
			if(y2 > border_top){
				return Position.make(x1, border_top);
			} else if(y2 < border_bottom){
				return Position.make(x1, border_bottom);
			} else {
				return Position.make(target.x, target.y);
			}
		}
		
		if(y1 == y2){
			if(x2 > border_right){
				return Position.make(border_right, y1);
			} else if(x2 < border_left){
				return Position.make(border_left, y1);
			} else {
				return Position.make(target.x, target.y);
			}
		}
		
		final float k = (y2-y1)/(x2-x1);
		final float b = y1 - k*x1;
		float x = x2;
		float y = y2;
		
		if(x2 > border_right){
			x = border_right;
			y = k*x + b;
			return getPossibleTarget(object, Position.make(x, y));
		} else if(x2 < border_left){
			x = border_left;
			y = k*x + b;
			return getPossibleTarget(object, Position.make(x, y));
		}
		
		if(y2 > border_top){
			y = border_top;
			x = (y-b)/k;
			return getPossibleTarget(object, Position.make(x, y));
		} else if(y2 < border_bottom){
			y = border_bottom;
			x = (y - b)/k;
			return getPossibleTarget(object, Position.make(x, y));
		}
		
		return Position.make(target.x, target.y);
	}
	
	public void step(float dt){
		for(GameObject object : getGameObjects()){
			step(object, dt);
		}
		callContacts();
	}
	
	public List<GameObject> getGameObjects(){
		return gameWorldObjects;
	}
	
	public Size getSize(){
		return gameWorldSize;
	}
	
	public void setBasicSpeedForObject(GameObject object, Vector basicSpeed){
		object.setBasicSpeed(basicSpeed);
	}
	
	protected void step(GameObject object, float dt){
		final Position target = objectTargets.get(object);
		
		if(object.isLifetimeExist()){
			object.setLifetime(object.getLifetime() - dt);
		}
		
		//effects
		List<GameEffect> removeEffectList = new ArrayList<GameEffect>();
		
		for(GameEffect effect : object.getAllEffects()){
			effect.setLifetime(effect.getLifetime() - dt);
			
			if(effect.getLifetime() < 0.0f){
				removeEffectList.add(effect);
			}
		}
		
		if(removeEffectList.size() != 0){
			removeAllEffectForObject(object, removeEffectList);
		}
		//effects
		
		if(target == null){
			stepWithoutTarget(object, dt);
		} else{
			stepWithTarget(object, target, dt);
		}
	}
		
	public void setEffect(GameObject object, EffectType type){
		final GameEffect effect = effects.get(type);
		object.setEffect(new GameEffect(effect.getType(), effect.getLifetime(), effect.getfSpeed(), 
				effect.getfAccelerate(), effect.getsSpeed(), effect.getsAccelerate()));
		
		Vector speed = object.getSpeed();
		Vector acceleration = object.getAcceleration();
		
		speed = Vector.sum(speed, Vector.makeFromAngle(effect.getsSpeed(), speed.calculateAngle()));
		speed = Vector.make(speed.x*effect.getfSpeed(), speed.y*effect.getfSpeed());
		
		if(speed.length() > object.getMaxSpeed()){
			speed = Vector.makeFromAngle(object.getMaxSpeed(), speed.calculateAngle());
		}
		
		acceleration = Vector.sum(acceleration, Vector.makeFromAngle(effect.getsAccelerate(), acceleration.calculateAngle()));
		acceleration = Vector.make(acceleration.x*effect.getfAccelerate(), acceleration.y*effect.getfAccelerate());
		
		object.setSpeed(speed);
		object.setAcceleration(acceleration);
	}
	
	protected void removeAllEffectForObject(GameObject object, List<GameEffect> removeList){
		List<GameEffect> currentEffects = object.getAllEffects();
		currentEffects.removeAll(removeList);
		object.removeAllEffects();
		
		for(GameEffect effect : currentEffects){
			setEffect(object, effect.getType());
		}
	}
		
	protected void elementaryMove(GameObject object, float dt){
		final Position position = object.getCenterPosition();
		final Vector speed = object.getSpeed();
		final Vector acceleration = object.getAcceleration();
		
		object.setCenterPosition(Position.make(position.x + speed.x*dt, position.y + speed.y*dt));
		object.setSpeed(Vector.make(speed.x + acceleration.x*dt, speed.y + acceleration.y*dt));
	}
	
	protected void stepWithoutTarget(GameObject gameObject, float dt){
		if(gameObject.getSpeed().length() == 0.0f){
			return;
		}
		
		float dtOutByX = timeToOutByX(gameObject);
		float dtOutByY = timeToOutByY(gameObject);
		float dtOut = Math.min(dtOutByX, dtOutByY);
		
		if(dt > dtOut){
			float dt_ = dt - dtOut;

			elementaryMove(gameObject, dtOut);
			
			if(dtOut == dtOutByX){
				gameObject.setSpeed(Vector.reverseX(gameObject.getSpeed()));
				gameObject.setBasicSpeed(Vector.reverseX(gameObject.getBasicSpeed()));
				mDirectionChangedListener.onDirectionChanged(new DirectionChangedEvent(gameObject));
			}
			
			if(dtOut == dtOutByY){
				gameObject.setSpeed(Vector.reverseY(gameObject.getSpeed()));
				gameObject.setBasicSpeed(Vector.reverseY(gameObject.getBasicSpeed()));
			}
						
			stepWithoutTarget(gameObject, dt_);
		} else{
			elementaryMove(gameObject, dt);
		}
	}
	
	protected void stopObject(GameObject object){
		object.setSpeed(Vector.zeroVector());
		object.setAcceleration(Vector.zeroVector());
	}
	
	protected void stepWithTarget(GameObject gameObject, Position target, float dt){
		final Position currentPosition = gameObject.getCenterPosition();
		final Vector currentSpeed = gameObject.getSpeed();
		
		if((Position.distance(currentPosition, target) < currentSpeed.length()*dt)){
			objectTargets.remove(gameObject);
			gameObject.setCenterPosition(target);
			stopObject(gameObject);
		} else{
			elementaryMove(gameObject, dt);
		}
	}
	
	protected float timeToOutByX(GameObject gameObject){
		final Vector speed = gameObject.getSpeed();
		
		if(speed.x < 0){
			return (gameObject.getCenterPosition().x - gameObject.getOccupiedArea().size.width / 2.0f) / Math.abs(speed.x);
		} else{
			return (gameWorldSize.width - gameObject.getOccupiedArea().size.width / 2.0f - gameObject.getCenterPosition().x) / Math.abs(speed.x);
		}
	}
	
	protected float timeToOutByY(GameObject gameObject){
		final Vector speed = gameObject.getSpeed();
		
		if(speed.y < 0){
			return (gameObject.getCenterPosition().y - gameObject.getOccupiedArea().size.height / 2.0f) / Math.abs(speed.y);
		} else{
			return (gameWorldSize.height - gameObject.getOccupiedArea().size.height / 2.0f - gameObject.getCenterPosition().y) / Math.abs(speed.y);
		}
	}
	
	protected void callContacts(){
		for(ContactEvent event: getAllContactEvents()){
			mContactListener.contant(event);
		}
	}
	
	protected List<ContactEvent> getAllContactEvents(){
		List<ContactEvent> events = new ArrayList<ContactEvent>();
		
		for(int i=0;i<getGameObjects().size()-1;i++){
			for(int j=i+1;j<getGameObjects().size();j++){
				if(RectArea.isIntersection(getGameObjects().get(i).getOccupiedArea(), 
						getGameObjects().get(j).getOccupiedArea(), 0.1f)){
					events.add(new ContactEvent(getGameObjects().get(i), getGameObjects().get(j)));
				}
			}
		}
		
		return events;
	}
			
	private final Size gameWorldSize;
	private List<GameObject> gameWorldObjects;
	private ContactListener mContactListener;
	private OnXDirectionChangedListener mDirectionChangedListener;
	private Map<GameObject, Position> objectTargets = new HashMap<GameObject, Position>();
	private Map<EffectType, GameEffect> effects = new HashMap<EffectType, GameEffect>();
}
